﻿using GrainManage.Server.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Schema;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Web;

namespace GrainManage.Server.Util
{
    public class ApiGenerator
    {
        private static readonly List<string> exceptedClassList = new List<string> { "List`1", "Dictionary`2" };
        private static readonly Dictionary<string, Type> controllerTypeMap = new Dictionary<string, Type>();
        private static readonly JsonSchemaGenerator schemaGenerator = new JsonSchemaGenerator();

        static ApiGenerator()
        {
            var assembly = Assembly.GetAssembly(typeof(GrainManage.Interface.IAccount));
            foreach (var item in assembly.ExportedTypes)
            {
                if (item.IsInterface && item.Name.StartsWith("I"))
                {
                    var name = item.Name.Substring(1);
                    controllerTypeMap[name] = item;
                }
            }
        }

        public static Dictionary<string, Type> ControllerType { get { return controllerTypeMap; } }

        public static ControllerModel GetApiInfo(string controllerName, bool isSchema, bool isGet)
        {
            var controller = new ControllerModel();
            controller.IsSchema = isSchema;
            controller.IsGet = isGet;
            var list=new List<string>(controllerTypeMap.Keys as IEnumerable<string>);
            list.Sort();
            controller.Controllers = list;
            controller.Name = controllerName;
            controller.Domain = GetDomain();
            Type classType = controllerTypeMap[controllerName];
            var methods = classType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
            controller.Methods = GetMethods(methods, isSchema);
            return controller;
        }

        private static List<GrainManage.Server.Models.ControllerModel.MethodModel> GetMethods(MethodInfo[] methods, bool isSchema)
        {
            var methodModels = new List<GrainManage.Server.Models.ControllerModel.MethodModel>();
            for (int i = 0; i < methods.Length; i++)
            {
                var method = methods[i];
                var parameters = method.GetParameters();
                var methodModel = new ControllerModel.MethodModel();
                methodModel.AnchorName = (i + 1).ToString();
                methodModel.Name = method.Name;

                if (parameters.Length == 1)
                {
                    methodModel.ParameterString = string.Format("{0} : {1}", parameters[0].Name, parameters[0].ParameterType.Name);
                    methodModel.InputString = isSchema ? GetSchema(parameters[0].ParameterType) : GetContent(parameters[0].ParameterType);
                    methodModel.OutputString = isSchema ? GetSchema(method.ReturnType) : GetContent(method.ReturnType);
                }
                else if (parameters.Length > 1)
                {
                    methodModel.ParaNameTypePair = GetParameterNameTypePair(parameters);
                    methodModel.ParaNameValuePair = methods.Length > 1 ? GetParameterNameValuePair(isSchema, parameters) : null;
                }
                methodModel.ReturnType = method.ReturnType.Name;
                methodModels.Add(methodModel);
            }
            return methodModels;
        }

        private static string GetSchema(Type classType)
        {
            var schemaContent = string.Empty;
            if (!classType.Namespace.StartsWith("System") || exceptedClassList.Contains(classType.Name))
            {
                var schema = schemaGenerator.Generate(classType);
                schemaContent = schema.ToString();
                schemaContent = System.Text.RegularExpressions.Regex.Replace(schemaContent, "\r\n *\"required\": true,", string.Empty);
                schemaContent = System.Text.RegularExpressions.Regex.Replace(schemaContent, ",\r\n *\"null\"", string.Empty);
            }
            return schemaContent;
        }

        private static string GetContent(Type classType)
        {
            string json = string.Empty;
            if (!classType.Namespace.StartsWith("System") || exceptedClassList.Contains(classType.Name))
            {
                var content = Activator.CreateInstance(classType);
                if (content != null)
                {
                    CreateContent(content, classType);
                    json = JsonConvert.SerializeObject(content, Formatting.Indented);
                }
            }
            return json;
        }

        private static void CreateContent<TContent>(TContent contentInstance, Type contentType) where TContent : class
        {
            if (!contentType.Namespace.StartsWith("System"))
            {
                var contentProperties = contentType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
                foreach (var contentProperty in contentProperties)
                {
                    var contentPropertyType = contentProperty.PropertyType;
                    if (!contentPropertyType.Namespace.StartsWith("System") || exceptedClassList.Contains(contentPropertyType.Name))
                    {
                        if (!contentPropertyType.Namespace.StartsWith("System") && contentPropertyType == contentType)   //防止死循环
                        {
                            break;
                        }
                        var contentPropertyInstance = Activator.CreateInstance(contentPropertyType);
                        contentProperty.SetValue(contentInstance, contentPropertyInstance, null);
                        CreateContent(contentPropertyInstance, contentPropertyType);
                    }
                    else if (contentPropertyType == typeof(string))
                    {
                        contentProperty.SetValue(contentInstance, string.Empty, null);
                    }
                }
            }
            else if (contentType.Name == "List`1")
            {
                var contentArgumentTypes = contentType.GetGenericArguments();
                if (contentArgumentTypes != null)
                {
                    foreach (var contentArgumentType in contentArgumentTypes)
                    {
                        if (!contentArgumentType.Namespace.StartsWith("System") || exceptedClassList.Contains(contentArgumentType.Name))
                        {
                            var contentArgumentInstance = Activator.CreateInstance(contentArgumentType);
                            var addMethod = contentType.GetMethod("Add");
                            addMethod.Invoke(contentInstance, new object[] { contentArgumentInstance });
                            CreateContent(contentArgumentInstance, contentArgumentType);
                        }
                        else if (contentArgumentType == typeof(string))
                        {
                            var addMethod = contentType.GetMethod("Add");
                            addMethod.Invoke(contentInstance, new object[] { "item1" });
                            addMethod.Invoke(contentInstance, new object[] { "item2" });
                        }
                    }
                }
            }
            else if (contentType.Name == "Dictionary`2")
            {
                var contentArgumentTypes = contentType.GetGenericArguments();
                if (contentArgumentTypes != null && contentArgumentTypes.Length == 2)
                {
                    var contentArgumentKeyType = contentArgumentTypes[0];
                    var contentArgumentValueType = contentArgumentTypes[1];
                    if (contentArgumentKeyType == typeof(string))
                    {
                        if (!contentArgumentValueType.Namespace.StartsWith("System") || exceptedClassList.Contains(contentArgumentValueType.Name))
                        {
                            var addMethod = contentType.GetMethod("Add");
                            var contentArgumentValueInstance = Activator.CreateInstance(contentArgumentValueType);
                            addMethod.Invoke(contentInstance, new object[] { "key", contentArgumentValueInstance });
                            CreateContent(contentArgumentValueInstance, contentArgumentValueType);
                        }
                        else if (contentArgumentValueType == typeof(string))
                        {
                            var addMethod = contentType.GetMethod("Add");
                            addMethod.Invoke(contentInstance, new object[] { "key1", "value1" });
                            addMethod.Invoke(contentInstance, new object[] { "key2", "value2" });
                        }
                    }
                    else if (contentArgumentKeyType == typeof(int) && contentArgumentValueType == typeof(string))
                    {
                        var addMethod = contentType.GetMethod("Add");
                        addMethod.Invoke(contentInstance, new object[] { 1, "value1" });
                        addMethod.Invoke(contentInstance, new object[] { 2, "value2" });
                    }
                }
            }
        }

        private static string GetParameterUrlString(ParameterInfo[] inputParames)
        {
            string paraPattern = string.Empty;
            if (inputParames.Length > 0 && inputParames[0].Name != "input")
            {
                foreach (var para in inputParames)
                {
                    paraPattern += (para.Name + "=" + para.DefaultValue + "&");
                }
                paraPattern = paraPattern.Remove(paraPattern.Length - 1, 1);
            }
            return paraPattern;
        }

        private static Dictionary<string, string> GetParameterNameTypePair(ParameterInfo[] inputParames)
        {
            Dictionary<string, string> paraPattern = null;
            if (inputParames.Length > 0)
            {
                paraPattern = new Dictionary<string, string>(inputParames.Length);
                foreach (var para in inputParames)
                {
                    paraPattern.Add(para.Name, para.ParameterType.Name);
                }
            }
            return paraPattern;
        }

        private static Dictionary<string, string> GetParameterNameValuePair(bool isSchema, ParameterInfo[] inputParames)
        {
            var paraValue = new Dictionary<string, string>(inputParames.Length);
            foreach (var para in inputParames)
            {
                var paraType = para.ParameterType;
                if (isSchema)
                {
                    paraValue.Add(para.Name, GetSchema(paraType));
                }
                else
                {
                    paraValue.Add(para.Name, GetContent(paraType));
                }
            }
            return paraValue;
        }

        private static string GetDomain()
        {
            var url = HttpContext.Current.Request.Url;
            var domain = string.Format("{0}://{1}", url.Scheme, url.Authority);
            return domain;
        }
    }
}